#include "linux/of.h"
#include "linux/io.h"
#include "linux/clocksource.h"
#include "linux/errno.h"
#include "linux/slab.h"
#include "linux/types.h"
#include "linux/interrupt.h"
#include "linux/init.h"
#include "linux/clockchips.h"


#define OFFSET_CR   0	  /**< Control register, offset: 0x0 */
#define OFFSET_SR	4     /**< Status register, offset: 0x4 */
#define OFFSET_LR	8     /**< Load register, offset: 0x8 */
#define OFFSET_CMPR 0xc   /**< Compare register, offset: 0xC */
#define OFFSET_CNR  0x10  /**< Counter register, offset: 0x10 */

struct imx_epit;

struct imx_epit
{
    int irq;
    unsigned int type;
    volatile void *base;
    struct clock_event_device ced;
    struct irqaction act;
};

static inline struct imx_epit *to_imx_timer(struct clock_event_device *ced)
{
	return container_of(ced, struct imx_epit, ced);
}


static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id)
{
	unsigned long value;
	struct clock_event_device *ced = dev_id;
	struct imx_epit *imxtm = to_imx_timer(ced);

	value = readl_relaxed(imxtm->base + OFFSET_SR);

	value |= (1 << 0);	//清除中断标志位
	writel_relaxed(value, imxtm->base + OFFSET_SR);

	ced->event_handler(ced);

	return IRQ_HANDLED;
}


static int mxc_clockevent_init(struct imx_epit *imxtm)
{
	struct clock_event_device *ced = &imxtm->ced;
	struct irqaction *act = &imxtm->act;

	ced->name = "mxc_timer1";
	ced->rating = 200;
	ced->irq = imxtm->irq;

	clockevents_config_and_register(ced);

	act->name = "i.MX Timer Tick";
	act->flags = IRQF_TIMER | IRQF_IRQPOLL;
	act->handler = mxc_timer_interrupt;
	act->dev_id = ced;

	return setup_irq(imxtm->irq, act);
}

static void mxc_epit_io_init(struct imx_epit *imxtm, unsigned int frac, unsigned int value)
{
	if(frac > 0xFFF)
		frac = 0xFFF;

	writel(0, imxtm->base + OFFSET_CR);
	writel(value, imxtm->base + OFFSET_LR); /* 倒数计数 */
	writel(0, imxtm->base + OFFSET_CMPR);  /* 比较寄存器，当计数器值和此寄存器值相等的话就会产生中断 */
	
	/*
     * CR寄存器:
     * bit25:24 01 时钟源选择Peripheral clock=66MHz
     * bit15:4  frac 分频值
     * bit3:	1  当计数器到0的话从LR重新加载数值
     * bit2:	1  比较中断使能
     * bit1:    1  初始计数值来源于LR寄存器值
     * bit0:    1  打开EPIT1
     */
	writel((1 << 24 | frac << 4 | 1 << 3 | 1 << 2 | 1 << 1 | 1 << 0), imxtm->base + OFFSET_CR);
}

static int mxc_timer_init_dt(struct device_node *np, unsigned int type)
{
    struct imx_epit *imxtm;
    int ret = 0;
    static int initialized = 0;

    if (initialized)
        return 0;

    imxtm = kzalloc(sizeof(*imxtm), GFP_KERNEL);
	if (!imxtm)
		return -ENOMEM;

    imxtm->base = of_iomap(np, 0);
	if (!imxtm->base)
	{
		kfree(imxtm);
		return -ENXIO;
	}

	imxtm->irq = irq_of_parse_and_map(np, 0);
	if (imxtm->irq <= 0)
	{
		kfree(imxtm);
		return -EINVAL;
	}
    imxtm->type = type;

	mxc_epit_io_init(imxtm, 0, 66000000 / 100);

	ret = mxc_clockevent_init(imxtm);
	if (ret)
	{
		kfree(imxtm);
		return ret;
	}

	initialized = 1;

    return 0;
}

static int __init imx6dl_timer_init_dt(struct device_node *np)
{
	return mxc_timer_init_dt(np, 0);
}

CLOCKSOURCE_OF_DECLARE(imx6ul_timer, "fsl,imx6ul-epit", imx6dl_timer_init_dt);
